package server

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/xhyonline/websocket/mod"
	"github.com/xhyonline/xutil/helper"
	"gopkg.in/olahol/melody.v1"
	"strconv"
	"sync/atomic"
	"time"
)

// HandlerMessage 处理消息
func (s *server) HandlerMessage(session *melody.Session, bytes []byte) {
	// 临时先接收用户基本信息
	tmp := new(mod.User)
	err := json.Unmarshal(bytes, tmp)
	if err != nil {
		s.SocketErrorOutputMarshal(session, fmt.Sprintf("解析用户基本信息失败 %s", err))
		return
	}
	// 获取真实用户实例
	user, err := s.GetUserByID(tmp.UID)
	if err != nil {
		s.SocketErrorOutputMarshal(session, fmt.Sprintf("%s", err))
		return
	}
	// 分析用户场景
	switch tmp.Scene {
	case 1: // 如果当用户刚进入大厅,我们需要给他返回在线的女生数与男生数
		s.SocketOutputMarshal(session, &output{
			Code: 100,
			Msg:  "返回成功",
			Data: s.IntoTheHall(),
		})
		return
	case 2: // 当用户填写了基本信息并且点击了匹配
		// 验证用户基本信息
		if tmp.NickName == "" || tmp.Sex == 0 || tmp.UID == "" || tmp.Mode == 0 {
			s.SocketErrorOutputMarshal(session, fmt.Sprintf("验证用户基本信息失败"))
			return
		}
		user.NickName = tmp.NickName
		// 验证成功,分析用户聊天模式
		switch tmp.Mode {
		case 1: // 当用户点击个人匹配时 (随机男女)
			user.Mode = 1
		case 2: // 当用户点击聊天室匹配时
			user.Mode = 2
		case 3: // 当用户点击异性匹配时
			user.Mode = 3
		}
		// TODO 此处需要防止用户刷接口
		if tmp.Sex == 1 {
			s.MaleCountAdd()
			user.Sex = 1
		} else {
			user.Sex = 2
			s.FemaleCountAdd()
		}
		// 加入等待匹配队列
		s.WaitQueue <- user
	case 3: // 用户正在聊天时发送的消息
		msg := new(struct {
			Message string `json:"message"`
		})
		err := json.Unmarshal(bytes, msg)
		if err != nil {
			s.SocketErrorOutputMarshal(session, fmt.Sprintf("%s", err))
			return
		}
		err = s.UserSendMessage(session, []byte(msg.Message))
		if err != nil {
			s.SocketErrorOutputMarshal(session, fmt.Sprintf("%s", err))
			return
		}
	case 4: // 用户点击了取消等待匹配按钮
		err := s.CancelWaite(session)
		if err != nil {
			s.SocketErrorOutputMarshal(session, fmt.Sprintf("%s", err))
			return
		}
		s.SocketOutputMarshal(session, &output{
			Code: 103,
			Msg:  "消息推送",
			Data: nil,
		})
	}
}

// IntoTheHall 刚进入大厅的那一刻我们需要返回数据
func (s *server) IntoTheHall() *mod.IntoTheHall {
	resp := &mod.IntoTheHall{
		MaleCount:   atomic.LoadInt32(s.MaleCount),
		FemaleCount: atomic.LoadInt32(s.FemaleCount),
		UsersCount:  atomic.LoadInt32(s.UserCount),
	}
	return resp
}

// UserSendMessage 当用户发送消息时的处理逻辑
func (s *server) UserSendMessage(session *melody.Session, message []byte) error {
	id, _ := session.Get("uid")
	user, err := s.GetUserByID(id.(string))
	if err != nil {
		log.Errorf("处理用户发送消息失败 %s", err)
		return fmt.Errorf("处理用户发送消息失败 %s", err)
	}
	// 向除了自己以外的用户广播消息
	room, err := s.GetRoomByRID(user.RoomID)
	if err != nil {
		log.Errorf("处理用户发送消息失败 %s", err)
		return fmt.Errorf("处理用户发送消息失败 %s", err)
	}
	send := &output{
		Code: 104,
		Msg:  "事件推送",
		Data: gin.H{
			"nick_name": user.NickName,
			"uid":       user.UID,
			"message":   string(message),
			"time":      time.Now().Unix(),
		},
	}
	msg, _ := json.Marshal(send)
	for _, item := range room.Users {
		if user.UID == item.UID {
			continue
		}
		_ = item.Session.Write(msg)
	}
	return nil
}

// HandleQueue 处理队列行为
func (s *server) HandleQueue() {
	for user := range s.WaitQueue {
		var isFind bool
		s.RoomGroup.Range(func(key, value interface{}) bool {
			room := value.(*mod.Room)
			// 房间没满且用户聊天模式和房间类型匹配
			if !room.IsFull && user.Mode == room.Type {
				// 如果该用户是异性匹配模式
				if user.Mode == 3 {
					// 查找房间另一个用户的性别
					for _, item := range room.Users {
						// 同性不匹配
						if item.Sex == user.Sex {
							// continue 换房间
							return true
						}
						// 找到就跳出
						break
					}
				}
				isFind = true
				room.Users[user.UID] = user
				room.UserCountAdd()
				user.RoomID = room.ID

				o := &output{
					Code: 101,
					Msg:  "事件推送",
				}
				// 房间广播
				if user.Mode == 2 {
					o.Data = gin.H{
						"mode":      user.Mode,
						"uid":       user.UID,
						"message":   fmt.Sprintf("热烈欢迎 %s 进入了房间", user.NickName),
						"nick_name": user.NickName,
						"sex":       user.Sex,
					}
				} else {
					var users = make([]struct {
						UID      string `json:"uid"`
						NickName string `json:"nick_name"`
						Sex      int    `json:"sex"`
					}, 0, 2)
					for _, v := range room.Users {
						users = append(users, struct {
							UID      string `json:"uid"`
							NickName string `json:"nick_name"`
							Sex      int    `json:"sex"`
						}{UID: v.UID, NickName: v.NickName, Sex: v.Sex})
					}
					o.Data = gin.H{
						"mode": user.Mode,
						"user": users,
					}
				}

				out, _ := json.Marshal(o)
				s.BroadcastMultiple(out, room.Users)
				// 当房间里人数为 2 且房间聊天类型为 2 人聊天室的
				if room.GetUserCount() == 2 && (room.Type == 1 || room.Type == 3) {
					room.IsFull = true
					return false
				}
				// 群聊聊天室 10 人一组
				if room.GetUserCount() == 10 && room.Type == 2 {
					room.IsFull = true
					return false
				}
			}
			return true
		})
		// 如果没有匹配到房间,就让用户自己建立房间
		if !isFind {
			s.CrateRoom(user)
		}
	}
}

// CrateRoom 根据用户的聊天模式来创建房间
func (s *server) CrateRoom(user *mod.User) {
	// 生成房间 ID
	rid := strconv.Itoa(helper.GetRandom(30000)) + "_" + strconv.Itoa(int(time.Now().UnixNano()))
	s.RoomGroup.Store(rid, &mod.Room{
		ID:    rid,
		Topic: s.Topics[helper.GetRandom(1000)%len(s.Topics)], // 生成话题
		Type:  user.Mode,
		Count: new(int32),
		Users: make(map[string]*mod.User),
	})
	user.RoomID = rid
	room, _ := s.GetRoomByRID(rid)
	room.UserCountAdd()
	room.Users[user.UID] = user
}

// RemoveRoom 删除房间
func (s *server) RemoveRoom() {
	for {
		// 定时垃圾回收
		s.RoomGroup.Range(func(key, value interface{}) bool {
			if value.(*mod.Room).GetUserCount() == 0 {
				s.RoomGroup.Delete(key)
			}
			return true
		})
		time.Sleep(time.Second * 30)
	}
}
